package com.djakapm.swing.component.table;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Vector;

import javax.swing.event.EventListenerList;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;


@SuppressWarnings("unchecked")
public class TableModel extends DefaultTableModel implements TableModelListener {
	private static final long serialVersionUID = -3861007860978559918L;
	private EventListenerList listenerList = new EventListenerList();
	private javax.swing.table.TableModel delegatedModel;
	private int[] sortedIndicies;
	private int sortColumn;
	private Comparator comparator;
	private Vector<Comparator> comparators;

	public TableModel (javax.swing.table.TableModel tm,Comparator c) {
		this.comparators = new Vector<Comparator>();
		this.delegatedModel = tm;
		this.delegatedModel.addTableModelListener (this);
		this.sortedIndicies = new int [0];
		this.setSortColumn(0);
		for(int i=0;i<tm.getColumnCount();i++) {
			this.comparators.addElement(c);
		}

	}
	
	public void setSortColumn (int i) {
		this.sortColumn = i;
		this.comparator = null;
		if(this.comparators == null || this.comparators.isEmpty()) {return;}
		this.comparator = this.comparators.get(this.sortColumn);
		this.resort();
	}

	public void setSortColumn (int i, boolean isSortAsc) {
		this.sortColumn = i;
		this.comparator = null;
		if(this.comparators == null || this.comparators.isEmpty()) {return;}
		this.comparator = this.comparators.get(this.sortColumn);
		this.resort(isSortAsc);
	}

	public int getSortColumn () {
		return this.sortColumn;
	}
	
	public void setComparatorForColumn (Comparator c) {
		this.comparators.addElement(c);
	}
	
	
	public void fireTableModelEvent(TableModelEvent e) {
		for(Object listener:this.listenerList.getListenerList()) {
			if (listener instanceof TableModelListener) {
				((TableModelListener) listener).tableChanged(e);
			}
		}
	}
	
	protected void resort() { 
		if (this.sortedIndicies.length != this.delegatedModel.getRowCount()) {	
			this.sortedIndicies = new int [this.delegatedModel.getRowCount()];
		}
		
		List<Object> sortMe = new ArrayList<Object>();
		for (int i=0; i<this.delegatedModel.getRowCount(); i++) {
			SortingDelegate sd = new SortingDelegate(this.delegatedModel.getValueAt(i, getSortColumn()), i);
			sortMe.add (sd);
		}
		
		SortingDelegateComparator sdc = new SortingDelegateComparator (this.comparator);
		Collections.sort(sortMe, sdc);
		for (int i=0; i<sortMe.size(); i++) {
			this.sortedIndicies[i] = ((SortingDelegate) sortMe.get(i)).row;
		}
		this.fireAllChanged(); 
	}
	
	protected void resort(boolean isSortAsc) { 
		if (this.sortedIndicies.length != this.delegatedModel.getRowCount()) {	
			this.sortedIndicies = new int [this.delegatedModel.getRowCount()];
		}
		
		List<Object> sortMe = new ArrayList<Object>();
		for (int i=0; i<this.delegatedModel.getRowCount(); i++) {
			SortingDelegate sd = new SortingDelegate(this.delegatedModel.getValueAt(i, getSortColumn()), i);
			sortMe.add(sd);
		}

		SortingDelegateComparator sdc = new SortingDelegateComparator (this.comparator,isSortAsc);
		Collections.sort(sortMe, sdc);
		for (int i=0; i<sortMe.size(); i++) {
			this.sortedIndicies[i] = ((SortingDelegate) sortMe.get(i)).row;
		}
		this.fireAllChanged(); 
	}
	
	protected void fireAllChanged() {
		TableModelEvent e = new TableModelEvent (this);
		this.fireTableModelEvent(e);
	} 
	
	public void addTableModelListener(TableModelListener l) {
		this.listenerList.add (TableModelListener.class, l);
	}
	
	public void tableChanged(TableModelEvent e) {
		switch (e.getType()) { 
			case TableModelEvent.DELETE:
			case TableModelEvent.INSERT: 
			case TableModelEvent.UPDATE: {
				this.resort();                         
				this.fireAllChanged(); 
				break; 
			}
		}
	}

	@Override
	public Class<?> getColumnClass(int columnIndex) {
		return (delegatedModel.getRowCount() > 0 ? 
				delegatedModel.getValueAt(0, columnIndex).getClass() : Object.class);
	}

	@Override
	public int getColumnCount() {
		return this.delegatedModel.getColumnCount(); 
	}

	@Override
	public String getColumnName(int columnIndex) {
		return this.delegatedModel.getColumnName(columnIndex); 
	}

	@Override
	public int getRowCount() {
		return (this.delegatedModel != null ? this.delegatedModel.getRowCount() : 0); 
	}

	private int getDelegatedRow (int row) {
		return sortedIndicies [row]; 
	} 
	
	@Override
	public Object getValueAt(int rowIndex, int columnIndex) {
		if(sortedIndicies.length == 0) {
			return this.delegatedModel.getValueAt (rowIndex,
					  columnIndex);
		}
		else {
			return this.delegatedModel.getValueAt (getDelegatedRow(rowIndex),
					  columnIndex);
		}
	}

	@Override
	public boolean isCellEditable(int rowIndex, int columnIndex) {
		return this.delegatedModel.isCellEditable (rowIndex, columnIndex);
	}

	@Override
	public void removeTableModelListener(TableModelListener l) {
		this.listenerList.remove (TableModelListener.class, l);
	}

	@Override
	public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
		this.delegatedModel.setValueAt (aValue, rowIndex, columnIndex); 
		
	}
	
	@Override
	public void addRow(Vector row) {
		if(this.delegatedModel instanceof DefaultTableModel) {
			((DefaultTableModel) this.delegatedModel).addRow(row);
		}
	}
}
